import * as uuid from "uuid";
import * as d3 from "d3";
import * as extra from "./extraData";
import { flextree } from "d3-flextree";
import * as p from "./props";

const getCenterOffsetX = () => {
  const container = document.getElementById("tree-container");
  const rect = container.getBoundingClientRect();
  return rect.width / 2;
};

/**
 * d3 拖拽缩放
 * @param {*} props
 * @param {*} element
 * @param {*} callback
 */
const zoomListner = (props, element, callback) => {
  const zoomListner = d3
    .zoom()
    .scaleExtent([0.2, 3])
    .on("zoom", (e) => {
      if (callback) callback(e);
    });

  const zoom = d3.select(element).call(zoomListner)
  zoom.on('dblclick.zoom', null);
  if(!props.wheelZoom){
    zoom.on('wheel.zoom', null)
  }

  const {top, left} = props;
  
  let translateX = left === p.defaultProps.left ? getCenterOffsetX() : left
  let translateY = top;

  d3.select(element)
    .transition()
    .call(zoomListner.transform, d3.zoomIdentity.translate(translateX, translateY))

  return zoomListner
};

/**
 * 手动缩放
 * @param {*} element 
 * @param {*} zoomListner 
 * @param {*} ratio 
 */
const zoom = (element, zoomListner, ratio) => {
  d3.select(element)
    .transition()
    .call(zoomListner.scaleTo, ratio)
}

/**
 * 画树节点延长线
 * @param {*} node 连接线的源节点
 * @param {*} props 属性
 */
const drawStretchLine = (sourceNode, props) => {
  const { hierarchyMargin, stretchLength } = props;
  const nodeHeight = Math.max(sourceNode.ySize - hierarchyMargin, 0);

  const p0 = [sourceNode.x, sourceNode.y + nodeHeight];
  const p1 = [sourceNode.x, p0[1] + stretchLength];

  const path = d3.path();
  path.moveTo(p0[0], p0[1]);
  path.lineTo(p1[0], p1[1]);
  return path.toString();
};

/**
 * 画knot节点延长线
 * @param {*} node 连接线的源节点
 * @param {*} props 属性
 */
const drawKnotStretchLine = (sourceNode, props) => {
  const { hierarchyMargin, stretchLength, knotStretchLength } = props;
  const knotheight = extra.getKnotHeight(sourceNode);
  const nodeHeight = Math.max(sourceNode.ySize - hierarchyMargin, 0);

  const p0 = [
    sourceNode.x,
    sourceNode.y + nodeHeight + stretchLength + knotheight,
  ];
  const p1 = [sourceNode.x, p0[1] + knotStretchLength];

  const path = d3.path();
  path.moveTo(p0[0], p0[1]);
  path.lineTo(p1[0], p1[1]);
  return path.toString();
};

/**
 * 画曲线连接线, 无knot
 *
 * @param {*} link
 * @param {*} props
 */
const drawCurveLinkWithoutKnot = (link, props) => {
  const { source, target } = link;
  const { hierarchyMargin, stretchLength } = props;
  const nodeHeight = Math.max(source.ySize - hierarchyMargin, 0);
  const k = Math.abs(target.y - source.y - nodeHeight);

  const p = [source.x, source.y + nodeHeight + stretchLength];

  const path = d3.path();
  path.moveTo(p[0], p[1]);
  path.bezierCurveTo(
    p[0],
    p[1] + k / 2,
    target.x,
    target.y - k / 2,
    target.x,
    target.y
  );
  return path.toString();
};

/**
 * 画曲线连接线, 有knot
 *
 * @param {*} link
 * @param {*} props
 */
const drawCurveLinkWithKnot = (link, props) => {
  const { source, target } = link;
  const { hierarchyMargin, stretchLength, knotStretchLength } = props;
  const nodeHeight = Math.max(source.ySize - hierarchyMargin, 0);
  const knotheight = extra.getKnotHeight(source);
  const k = Math.abs(
    target.y - source.y - nodeHeight - knotheight - knotStretchLength
  );

  const p = [
    source.x,
    source.y + nodeHeight + stretchLength + knotheight + knotStretchLength,
  ];

  const path = d3.path();
  path.moveTo(p[0], p[1]);
  path.bezierCurveTo(
    p[0],
    p[1] + k / 2,
    target.x,
    target.y - k / 2,
    target.x,
    target.y
  );
  return path.toString();
};

/**
 * 画曲线连接线
 *
 * @param {*} link
 * @param {*} props
 */
const drawCurveLink = (link, props) => {
  const { showKnot } = props;
  if (showKnot) {
    return drawCurveLinkWithKnot(link, props);
  } else {
    return drawCurveLinkWithoutKnot(link, props);
  }
};

/**
 * 查找某个节点所有子节点中, 最小、最大的x坐标
 *
 * @param {*} node
 */
const findLimitXOfChildren = (node) => {
  const xArray = node.children.map((item) => item.x);
  return {
    minX: Math.min(...xArray),
    maxX: Math.max(...xArray),
  };
};

/**
 * 画直线连接线，横线，无knot
 *
 * @param {*} link
 * @param {*} props
 */
const drawHStraightLineWithoutKnot = (source, props) => {
  const { hierarchyMargin, stretchLength } = props;
  const nodeHeight = Math.max(source.ySize - hierarchyMargin, 0);
  const { minX, maxX } = findLimitXOfChildren(source);

  const p0 = [minX, source.y + nodeHeight + stretchLength];
  const p1 = [maxX, p0[1]];

  const path = d3.path();
  path.moveTo(p0[0], p0[1]);
  path.lineTo(p1[0], p1[1]);
  return path.toString();
};

/**
 * 画直线连接线，横线, 有knot,
 *
 * @param {*} link
 * @param {*} props
 */
const drawHStraightLineWithKnot = (source, props) => {
  const { hierarchyMargin, stretchLength, knotStretchLength } = props;
  const nodeHeight = Math.max(source.ySize - hierarchyMargin, 0);
  const { minX, maxX } = findLimitXOfChildren(source);
  const knotheight = extra.getKnotHeight(source);

  const p0 = [
    minX,
    source.y + nodeHeight + stretchLength + knotheight + knotStretchLength,
  ];
  const p1 = [maxX, p0[1]];

  const path = d3.path();
  path.moveTo(p0[0], p0[1]);
  path.lineTo(p1[0], p1[1]);
  return path.toString();
};

/**
 * 画直线连接线, 横线
 *
 * @param {*} source
 * @param {*} props
 * @returns
 */
const drawHStraightLine = (source, props) => {
  const { showKnot } = props;
  if (showKnot) {
    return drawHStraightLineWithKnot(source, props);
  } else {
    return drawHStraightLineWithoutKnot(source, props);
  }
};

/**
 * 画直线连接线, 竖线, 无knot
 *
 * @param {*} link
 * @param {*} props
 * @returns
 */
const drawVStraightLineWithoutKnot = (link, props) => {
  const { target } = link;
  const { hierarchyMargin, stretchLength } = props;
  const marginHeight = hierarchyMargin - stretchLength;
  const p0 = [target.x, target.y - marginHeight];
  const p1 = [target.x, target.y];

  const path = d3.path();
  path.moveTo(p0[0], p0[1]);
  path.lineTo(p1[0], p1[1]);
  return path.toString();
};

/**
 * 画直线连接线, 竖线, 有knot
 *
 * @param {*} link
 * @param {*} props
 * @returns
 */
const drawVStraightLineWithKnot = (link, props) => {
  const { source, target } = link;
  const { hierarchyMargin, stretchLength, knotStretchLength } = props;
  const marginHeight = hierarchyMargin - stretchLength;
  const knotheight = extra.getKnotHeight(source);
  const p0 = [
    target.x,
    target.y - marginHeight + knotheight + knotStretchLength,
  ];
  const p1 = [target.x, target.y];

  const path = d3.path();
  path.moveTo(p0[0], p0[1]);
  path.lineTo(p1[0], p1[1]);
  return path.toString();
};

/**
 * 画直线连接线, 竖线
 *
 * @param {*} source
 * @param {*} props
 * @returns
 */
const drawVStraightLine = (link, props) => {
  const { showKnot } = props;
  if (showKnot) {
    return drawVStraightLineWithKnot(link, props);
  } else {
    return drawVStraightLineWithoutKnot(link, props);
  }
};

/**
 * 查询连接线的源节点
 * @param {*} links
 */
const findLinkSources = (links) => {
  const allSources = links.map((link) => link.source);
  let sources = [];
  allSources.forEach((source) => {
    if (sources.every((s) => s.id != source.id)) {
      sources.push(source);
    }
  });
  return sources;
};

/**
 * 计算knot元素的x坐标
 * @param {*} node
 * @param {*} props
 */
const calcKnotX = (node, props) => {
  const knotWidth = extra.getKnotWidth(node);
  return node.x - knotWidth / 2;
};

/**
 * 计算knot元素的y坐标
 * @param {*} node
 * @param {*} props
 */
const calcKnotY = (node, props) => {
  const { hierarchyMargin, stretchLength } = props;
  return node.y + node.ySize - hierarchyMargin + stretchLength;
};

const draw = (treeData, props) => {
  const { hierarchyMargin, neighborMargin } = props;
  const layout = flextree({
    children: (data) => {
      return p.getChildrenData(props, data)
    },
    nodeSize: (node) => {
      const width = extra.getNodeWidth(node);
      const height = extra.getNodeHeight(node);
      return [width, height + hierarchyMargin];
    },
    spacing: (nodeA, nodeB) => {
      return neighborMargin || Math.pow(nodeA.path(nodeB).length, 2);
    },
  });
  const tree = layout.hierarchy(treeData);
  layout(tree);
  const descendants = tree.descendants();
  //树节点
  const nodes = descendants
    .filter((node) => !extra.getNodeCollapsed(node))
    .map((node) => {
      node.id = uuid.v4();
      return node;
    });
  const links = tree.links();
  //所有连接源节点
  const linkSources = findLinkSources(links);
  //节点延长线
  const stretchLines = linkSources.filter((node) => {
    let result = true;
    if(p.isClickTreeNodeCollapsedWay(props)){
      result = !extra.getNodeRootCollapsed(node) && !extra.getNodeCollapsed(node)
    }
    if(p.isClickKnotNodeCollapsedWay(props)){
      result = !extra.getNodeCollapsed(node)
    }
    return result
  });
  //横向直线连接线
  const straightHLines = linkSources.filter((node) => !extra.getNodeCollapsed(node) && !extra.getNodeRootCollapsed(node));
  //竖向直线连接线
  const straightVLines = links.filter(({target}) => !extra.getNodeCollapsed(target))
  //曲线连接线
  const curveLines = straightVLines
  //连接节点
  const knots = stretchLines;
  //连接点延长线
  const knotStretchLines = straightHLines
  return {
    nodes,
    stretchLines,
    straightHLines,
    straightVLines,
    curveLines,
    knots,
    knotStretchLines
  };
};

/**
 * 根据dom元素实际尺寸调整节点的尺寸
 * @param {*} nodes
 * @param {*} scale
 */
const reSize = (nodes, scale, nodeClass) => {
  document.querySelectorAll(`.${nodeClass}`).forEach((element) => {
    const rect = element.getBoundingClientRect();
    const nodeId = element.dataset.nodeId;
    const node = nodes.find((node) => node.id === nodeId);
    if(node){
      extra.setNodeWidth(node, rect.width / scale);
      extra.setNodeHeight(node, rect.height / scale);
    }
  });

  document.querySelectorAll(".knot-container").forEach((element) => {
    const rect = element.getBoundingClientRect();
    const nodeId = element.dataset.nodeId;
    const node = nodes.find((node) => node.id === nodeId);
    extra.setKnotWidth(node, rect.width / scale);
    extra.setKnotHeight(node, rect.height / scale);
  });
};

/**
 * 展开或折叠节点
 * @param {*} rootNode
 * @param {*} collapsed
 */
const collapseNode = (rootNode, collapsed) => {
  if(rootNode.hasChildren) {
    if (collapsed) {
      extra.setNodeRootCollapsed(rootNode, true);
      rootNode.descendants().forEach((node) => {
        if (rootNode.id !== node.id) {
          extra.setNodeCollapsed(node, true);
        }
      });
    } else {
      rootNode.descendants().forEach((node) => {
        extra.setNodeCollapsed(node, false);
        extra.setNodeRootCollapsed(node, false);
      });
    }
  }
};

export {
  draw,
  reSize,
  calcKnotX,
  calcKnotY,
  zoom,
  zoomListner,
  drawStretchLine,
  drawKnotStretchLine,
  drawCurveLink,
  drawHStraightLine,
  drawVStraightLine,
  collapseNode,
};
